﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Configuration;
using MongoDB.Configuration;
using System.Web;
using MongoSecuretyProvider;


namespace MongoDB.Securety
{   
    
        public class MongoMembershipProvider : MembershipProvider
        {
            

            string UserCollectioName = "system.users";
            const string PwdCookieKey = "PwdCookieKey";
            public string _ConnectionString;            
            public string _ApplicationName;

            private bool _EnablePasswordReset;
            private bool _RequiresQuestionAndAnswer;
            private string _PasswordStrengthRegEx;

            private string _providerName = "MongoMembershipProvider";
            private int _MaxInvalidPasswordAttempts;
            private int _MinRequiredNonAlphanumericChars;
            private int _MinRequiredPasswordLength;
            private MembershipPasswordFormat _PasswordFormat;


            
            private string adminUserName;           // имя админа
            private string admPwd;                // пароль            
            public string databaseName;
            public string hostName;

            

            public string DatabaseName
            {
                get
                {
                    return databaseName ;
                }
            }

            public string AdminUserName
            {
                get
                {
                    return adminUserName;
                }
            }

            public string AdmPwd
            {
                get
                {
                    return admPwd;
                }
            }

            /// <summary>
            /// Строка соединения соотв. администратору.
            /// </summary>
            public MongoConnectionStringBuilder AdmConnectionStringBuilder
            {
                get
                {
                    MongoConnectionStringBuilder builder = new MongoConnectionStringBuilder()
                {
                    Username = AdminUserName ,
                    Password = AdmPwd 
                };

                    return builder;

            } }




            public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
            {

                if (config == null)
                {
                    throw new ArgumentNullException("config");
                }                                

                // Initialize the base class
                base.Initialize(name, config);
                _ApplicationName = "DefaultApp";
                _EnablePasswordReset = false;
                _PasswordStrengthRegEx = @"[\w| !§$%&/()=\-?\*]*";
                _MaxInvalidPasswordAttempts = 3;
                _MinRequiredNonAlphanumericChars = 1;
                _MinRequiredPasswordLength = 5;
                _RequiresQuestionAndAnswer = false;
                //_PasswordFormat = MembershipPasswordFormat.Hashed;
                _PasswordFormat = MembershipPasswordFormat.Clear;


                foreach (string key in config)
                {
                    switch (key.ToLower())
                    {
                        case "databasename":
                            databaseName = config[key].ToString();
                            break;

                        case "hostname":
                            hostName = config[key].ToString();
                            break;

                        case "name":
                            _providerName = config[key].ToString();
                            break;
                    }
                }



            }

            #region Properties

            public override string ApplicationName
            {
                get
                {
                    return _ApplicationName;
                }
                set
                {
                    _ApplicationName = value;

                }
            }

            public override bool EnablePasswordReset
            {
                get { return _EnablePasswordReset; }
            }

            public override bool EnablePasswordRetrieval
            {
                get
                {
                    if (this.PasswordFormat == MembershipPasswordFormat.Hashed)
                        return false;
                    else
                        return true;
                }
            }

            public override int MaxInvalidPasswordAttempts
            {
                get { return _MaxInvalidPasswordAttempts; }
            }

            public override int MinRequiredNonAlphanumericCharacters
            {
                get { return _MinRequiredNonAlphanumericChars; }
            }

            public override int MinRequiredPasswordLength
            {
                get { return _MinRequiredPasswordLength; }
            }

            public override int PasswordAttemptWindow
            {
                get { return 20; }
            }

            public override MembershipPasswordFormat PasswordFormat
            {
                get { return _PasswordFormat; }
            }

            public override string PasswordStrengthRegularExpression
            {
                get
                {
                    return _PasswordStrengthRegEx;
                }
            }

            public override bool RequiresQuestionAndAnswer
            {
                get { return _RequiresQuestionAndAnswer; }
            }

            public override bool RequiresUniqueEmail
            {
                get { return true; }
            }

            #endregion

            #region Methods

            /// <summary>
            /// возвращает connectionstring текущего пользователя
            /// </summary>
            /// <returns></returns>
            public static MongoConnectionStringBuilder getCurrentConnectionString(string databaseName)
            {
                
                //текущий пользователь identity?
                if (!HttpContext.Current.User.Identity.IsAuthenticated)
                    throw new Exception("Текущий пользователь не аутентифицирован");                

                MongoConnectionStringBuilder builder = new MongoConnectionStringBuilder()
                {
                    Username = HttpContext.Current.User.Identity.Name ,
                    Password = getPassword (),
                    Database = databaseName

                };
                return builder;
            }

            public MongoConnectionStringBuilder getCurrentConnectionString()
            {
                
                MongoConnectionStringBuilder builder = new MongoConnectionStringBuilder()
                {
                    Username = HttpContext.Current.User.Identity.Name,
                    Password = getPassword(),
                    Database = this.databaseName 

                };
                return builder;


            }


            public override MembershipUser CreateUser(string username, string password,
                string email, string passwordQuestion,
                string passwordAnswer, bool isApproved,
                object providerUserKey, out MembershipCreateStatus status)
            {

                MongoConnectionStringBuilder builder = getCurrentConnectionString(databaseName );

                using (Mongo mongo = new Mongo(builder.ToString()))
                {
                    try
                    {
                        mongo.Connect();                                                
                        IMongoDatabase db = mongo[databaseName];
                        db.Metadata.RemoveUser(username);
                        db.Metadata.AddUser(username, password);
                        
                        status = MembershipCreateStatus.Success;
                        MembershipUser usr = GetNewUser(username);
                        return usr;


                    }
                    catch(Exception e)
                    {
                        if (e.Message.IndexOf("already exists in this database") > -1)
                            status = MembershipCreateStatus.DuplicateUserName;
                        else 
                            status = MembershipCreateStatus.ProviderError;
                        return null;
                    }

                    finally
                    {
                        mongo.Disconnect();
                    }
                }
            }
            private MembershipUser GetNewUser(string username)
            {
                MembershipUser usr = new MembershipUser(_providerName , username, username, string.Empty, string.Empty, string.Empty, false, false, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue);
                return usr;
            }
            public override bool DeleteUser(string username, bool deleteAllRelatedData)
            {
                try
                {
                    //пользователь не авторизован
                    if (!HttpContext.Current.User.Identity.IsAuthenticated)
                        throw new Exception();

                    //епсли пользователь пытается удалить сам себя - ощибка 
                    if (username== HttpContext.Current.User.Identity.Name)
                        throw new Exception();

                }
                catch
                {

                    return false;
                }

                    
                
                MongoConnectionStringBuilder builder = getCurrentConnectionString(databaseName );

                using (Mongo mongo = new Mongo(builder.ToString()))
                {
                    try
                    {
                        mongo.Connect();
                        IMongoDatabase db = mongo[databaseName];
                        db.Metadata.RemoveUser (username );
                        
                        return true;
                    }
                    catch
                    {
                        
                        return false;
                    }

                    finally
                    {
                        mongo.Disconnect();
                    }
                }

            }

            public override MembershipUser GetUser(string username, bool userIsOnline)
            {
                int i0 = 0, i1 = 0, i2 = 0;
                foreach (MembershipUser u in FindUsersByName(username, i0, i1, out i2))
                {
                    return u;
                }
                return null;
            }

            public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override string GetUserNameByEmail(string email)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override void UpdateUser(MembershipUser user)
            {
                throw new Exception("The method or operation is not implemented.");
            }


            
            
            public override bool ValidateUser(string username, string password)
            {
                return ValidateUser(username, password, true);
            }

            public bool ValidateUser(string username, string password, bool opSetPasswordCookie)
            {
                // формируем подключение
                MongoConnectionStringBuilder builder = new MongoConnectionStringBuilder()
                {
                    Username = username,
                    Password = password,
                    Database = databaseName

                };

                using (Mongo mongo = new Mongo(builder.ToString()))
                {
                    try
                    {
                        //если сервер не доступен - исключение
                        mongo.TryConnect();

                        //любая операция с неавторизованным пользователем - приведет к исключению
                        IMongoDatabase db = mongo[databaseName];
                        Document d = db.Metadata.FindUser(username);

                        //проверить пользователя на блокировку (если есть поле isLocked и оно true - блокирован)
                        if (d["isLock"] != null)
                            if (d["isLock"].ToString().ToUpper() == "TRUE")
                                return false;


                        //если все хорошо - ассоциируем текущую строку подключниея с пользователем
                        if (opSetPasswordCookie)
                                setPassword(password);

                        return true;
                    }
                    catch
                    {
                        return false;
                    }

                    finally
                    {
                        mongo.Disconnect();
                    }
                }
            }

            
            
            public override bool ChangePassword(string username, string oldPassword, string newPassword)
            {
                bool isOk = false;
                //законектиться со старым паролем - если все нормально - удаляем добавляем 
                if (ValidateUser(username, oldPassword))
                {
                    // Формируем пользователя
                    MongoConnectionStringBuilder builder = getCurrentConnectionString(databaseName );

                    using (Mongo mongo = new Mongo(builder.ToString()))
                    {
                        try
                        {
                            mongo.Connect();
                            IMongoDatabase db = mongo[databaseName];
                            db.Metadata.RemoveUser(username);
                            db.Metadata.AddUser (username,newPassword);

                            return true;
                        }
                        catch
                        {

                            return false;
                        }

                        finally
                        {
                            mongo.Disconnect();
                        }
                    }


                }
                return isOk;

            }

            public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
            {
                    MembershipUserCollection result = new MembershipUserCollection(); 
                    totalRecords = result.Count;
                                 
                    MongoConnectionStringBuilder builder = getCurrentConnectionString(databaseName );

                    using (Mongo mongo = new Mongo(builder.ToString()))
                    {
                        try
                        {
                            mongo.Connect();
                            IMongoDatabase db = mongo[databaseName];
                            IMongoCollection coll= db.GetCollection(UserCollectioName);
                            Document q = new Document();
                            q.Add("user", new MongoRegex("^"+usernameToMatch));
                            ICursor  itmArr = coll.Find (q);                            
                            if(null==itmArr)
                                    return result;

                            foreach (Document item in itmArr.Documents)                            
                            {
                                object r = item.Values.First();
                                    result.Add(GetNewUser(usernameToMatch));

                            }

                            totalRecords = result.Count;
                            return result;
                        }
                        catch
                        {

                            totalRecords = -1;
                            return null;
                        }

                        finally
                        {
                            mongo.Disconnect();
                        }
                    }

            }

            public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override int GetNumberOfUsersOnline()
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override string GetPassword(string username, string answer)
            {
                throw new Exception("The method or operation is not implemented.");
            }


            public override string ResetPassword(string username, string answer)
            {
                throw new Exception("The method or operation is not implemented.");
            }

            public override bool UnlockUser(string userName)
            {
                LockUnlockUser(userName, false);
                return true;
            }

            public void LockUser(string userName)
            {
                LockUnlockUser(userName, true);
            }

            private void LockUnlockUser(string userName,bool isLock)
            {
                MongoConnectionStringBuilder builder = getCurrentConnectionString(databaseName);

                using (Mongo mongo = new Mongo(builder.ToString()))
                {
                    try
                    {
                        mongo.Connect();
                        IMongoDatabase db = mongo[databaseName];
                        IMongoCollection coll = db.GetCollection(UserCollectioName);
                        Document math = new Document();
                        math.Add("user", userName);
                        Document upd = new Document();
                        upd.Add("isLock", isLock);
                        coll.FindAndModify(upd, math);
                    }
                    finally
                    {
                        mongo.Disconnect();
                    }
                }
            }




            #endregion

            #region Private Helper Methods
            /// <summary>
            /// внедряет в текущий запрос куку с паролем - зашифрованным на ключе сеанса
            /// </summary>
            /// <param name="password"></param>
            private static  void setPassword(string password)
            {
                byte[] bytes = new UTF8Encoding().GetBytes(password);

                password = CookieProtectionHelperWrapper.Encode(CookieProtection.Encryption, bytes, bytes.Length);


                HttpCookie _Pwd = new HttpCookie(PwdCookieKey, password);
                if (null == HttpContext.Current.Response.Cookies[PwdCookieKey])
                    HttpContext.Current.Response.AppendCookie(_Pwd);
                else
                    HttpContext.Current.Response.SetCookie(_Pwd);
            }


            /// <summary>
            /// Вернуть пароль текущего пользователя
            /// </summary>
            private static string  getPassword()
            {
                if (null == HttpContext.Current.Request.Cookies[PwdCookieKey])
                    return string .Empty ;

                string s = HttpContext.Current.Request.Cookies[PwdCookieKey].Value;
                byte[] bytes = CookieProtectionHelperWrapper.Decode(CookieProtection.Encryption, s);
                System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
                s = enc.GetString(bytes);
                return s;
            }

            #endregion

            

        }
}

